/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/**
 * @version mempool.c 0.0.1
 * @package Comoro
 * @author Dr.NP <np@bsgroup.org>
 * 
 * Center server memory pool
 * Client's data and runner info storage
 */

#include "bspd.h"

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#include <stdlib.h>
#include <string.h>

#ifdef HAVE_MALLOC_H
#ifndef __OpenBSD__
#include <malloc.h>
#endif
#endif

struct client_item *c_pool;
struct group_item *g_pool;
struct room_item *r_pool;

int c_pool_size = BASE_POOL_SIZE;
int g_pool_size = BASE_POOL_SIZE;
int r_pool_size = BASE_POOL_SIZE;

int c_total;
int g_total;
int r_total;

/* Initialize memory pool */
int mempool_init()
{
	int i;
	
	// Memory space initialize
	c_pool = (struct client_item *) malloc(c_pool_size * sizeof(struct client_item));
	g_pool = (struct group_item *) malloc(g_pool_size * sizeof(struct group_item));
	r_pool = (struct room_item *) malloc(r_pool_size * sizeof(struct room_item));
	
	if (!c_pool || !g_pool || !r_pool)
	{
		fprintf(stderr, "Memory pool initialized failed!\n");
		log_add("Memory pool initialized failed");
		exit(EXIT_MEMORY_ERROR);
	}
	
	// Zero-fill
	
	memset(c_pool, 0, sizeof(c_pool));
	memset(g_pool, 0, sizeof(g_pool));
	memset(r_pool, 0, sizeof(r_pool));
	c_total = 0;
	g_total = 0;
	r_total = 0;
	
	for (i = 0; i < c_pool_size; i ++)
	{
		g_pool[i].group_id = -1;
		r_pool[i].room_id = -1;
	}

	fprintf(stderr, "Memory pool initialized\n");
	log_add("Memory pool initialized");
	
	return RTN_SUCCESS;
}

/* Add client */
int mempool_add_client(int client_id, unsigned long long user_id, char *username)
{
	struct client_item *ci = (struct client_item *) NULL;
	int i;
	
	while (client_id >= c_pool_size)
	{
		// Enlarge client pool
		struct client_item *n = realloc(c_pool, c_pool_size * 2 * sizeof(struct client_item));
	
		if (!n)
		{
			log_add("Client pool enlarge error");
			return RTN_MEMORY_ERROR;
		}
		
		memset(&n[c_pool_size], 0, c_pool_size * sizeof(struct client_item));
		c_pool = n;
		
		c_pool_size *= 2;
	}
	
	ci = &c_pool[client_id];
	memset(ci, 0, sizeof(struct client_item));
	ci->curr_runner_id = -1;
	ci->curr_room_id = -1;
	ci->curr_group_id = -1;
	
	for (i = 0; i < MAX_CLIENT_GROUPS; i ++)
	{
		ci->in_groups[i] = -1;
	}
	
	ci->client_id = client_id;
	ci->login_time = time((time_t *) NULL);
	ci->user_id = user_id;
	
	if (username)
	{
		strncpy(ci->user_name, username, MAX_USERNAME_LENGTH);
	}

	c_total ++;
	
	return client_id;
}

/* Set client's current runner */
int mempool_set_client_runner(int client_id, int runner_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		ci->curr_runner_id = runner_id;
	}

	fprintf(stderr, "Client %d set runner as %d\n", client_id, runner_id);
	
	return RTN_SUCCESS;
}

/* Set client's current room */
int mempool_set_client_room(int client_id, int room_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		ci->curr_room_id = room_id;
	}

	fprintf(stderr, "Client %d set room as %d\n", client_id, room_id);
	
	return RTN_SUCCESS;
}

/* Set client's current group */
int mempool_set_client_group(int client_id, int group_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		ci->curr_group_id = group_id;
	}

	fprintf(stderr, "Client %d set group as %d\n", client_id, group_id);
	
	return RTN_SUCCESS;
}

/* Set client's user id */
int mempool_set_client_uid(int client_id, unsigned long long user_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		ci->user_id = user_id;
	}
	
	return RTN_SUCCESS;
}

/* Set client's username */
int mempool_set_client_uname(int client_id, char *username)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id && username)
	{
		// Used
		strncpy(ci->user_name, username, MAX_USERNAME_LENGTH);
	}
	
	return RTN_SUCCESS;
}

/* Set client's extends data */
int mempool_set_client_extends(int client_id, int index, unsigned long long data)
{
	if (client_id >= c_pool_size || client_id < 0 || index >= MAX_CLIENT_EXTENDS || index < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		ci->extends_pool[index] = data;
	}
	
	return RTN_SUCCESS;
}

/* Get client's current runner */
int mempool_get_client_runner(int client_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		return ci->curr_runner_id;
	}
	
	return RTN_GENERAL;
}

/* Get client's current room */
int mempool_get_client_room(int client_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		return ci->curr_room_id;
	}
	
	return RTN_GENERAL;
}

/* Get client's current group */
int mempool_get_client_group(int client_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		return ci->curr_group_id;
	}
	
	return RTN_GENERAL;
}

/* Get client's user id */
unsigned long long mempool_get_client_uid(int client_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		return ci->user_id;
	}
	
	return 0;
}

/* Get client's username */
char * mempool_get_client_uname(int client_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return (char *) NULL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		return ci->user_name;
	}
	
	return (char *) NULL;
}

/* Get client's extends data */
unsigned long long mempool_get_client_extends(int client_id, int index)
{
	if (client_id >= c_pool_size || client_id < 0 || index >= MAX_CLIENT_EXTENDS || index < 0)
	{
		return 0;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	if (ci->client_id == client_id)
	{
		// Used
		return ci->extends_pool[index];
	}
	
	return 0;
}

/* Add group to client */
int mempool_add_client_group(int client_id, int group_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	int i;
	
	if (ci->client_id == client_id)
	{
		// Used
		for (i = 0; i < MAX_CLIENT_GROUPS; i ++)
		{
			if (ci->in_groups[i] == -1)
			{
				// An empty slot
				ci->in_groups[i] = group_id;
				
				return i;
			}
		}
	}
	
	return RTN_UNKNOWN;
}

/* Delete group from client */
int mempool_del_client_group(int client_id, int group_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	int i, ct = 0;
	
	if (ci->client_id == client_id)
	{
		// Used
		for (i = 0; i < MAX_CLIENT_GROUPS; i ++)
		{
			if (ci->in_groups[i] == group_id)
			{
				ci->in_groups[i] = -1;
				ct ++;
			}
		}
	}
	
	return ct;
}
	
/* Delete client */
int mempool_del_client(int client_id)
{
	if (client_id >= c_pool_size || client_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct client_item *ci = &c_pool[client_id];
	
	memset(ci, 0, sizeof(struct client_item));

	c_total --;
	
	return client_id;
}

/* Add group */
int mempool_add_group(char *group_name, char *group_password)
{
	struct group_item *gi = (struct group_item *) NULL;
	int group_id = -1;
	int i;
	
	for (i = 0; i < g_pool_size; i ++)
	{
		gi = &g_pool[i];
		
		if (gi->group_id < 0)
		{
			// Empty
			group_id = i;
			break;
		}
	}
	
	if (group_id < 0)
	{
		// Enlarge group pool
		struct group_item *n = realloc(g_pool, g_pool_size * 2 * sizeof(struct group_item));
		if (!n)
		{
			log_add("Group pool enlarge error");
			return RTN_MEMORY_ERROR;
		}
		
		memset(&n[g_pool_size], 0, g_pool_size * sizeof(struct group_item));
		g_pool = n;
		
		for (i = 0; i < g_pool_size; i ++)
		{
			g_pool[g_pool_size + i].group_id = -1;
		}
		
		group_id = g_pool_size;
		gi = &g_pool[group_id];
		
		g_pool_size *= 2;
	}
	
	gi->group_id = group_id;
	
	if (group_name)
	{
		strncpy(gi->group_name, group_name, MAX_USERNAME_LENGTH);
	}
	
	if (group_password && strlen(group_password))
	{
		strncpy(gi->password, group_password, MAX_USERNAME_LENGTH);
	}

	fprintf(stderr, "Group %s created as %d\n", group_name, group_id);

	g_total ++;
	
	return group_id;
}

/* Return client list in group */
int * mempool_list_group_client(int group_id, int *len)
{
	int i, ct = 0;
	int b = 16;
	int *ret = malloc(b * sizeof(int));
	
	for (i = 0; i < c_pool_size; i ++)
	{
		if (c_pool[i].login_time && group_id == c_pool[i].curr_group_id)
		{
			// Record i
			if (ct >= b)
			{
				// Enlarge buffer
				ret = realloc(ret, b * 2 * sizeof(int));
				b *= 2;
			}
			
			ret[ct] = i;
			ct ++;
		}
	}
	
	*len = ct;
	
	return ret;
}

/* Set group's current runner */
int mempool_set_group_runner(int group_id, int runner_id)
{
	if (group_id >= g_pool_size || group_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct group_item *gi = &g_pool[group_id];
	
	gi->curr_runner_id = runner_id;

	fprintf(stderr, "Group %d set runner as %d\n", group_id, runner_id);
	
	return RTN_SUCCESS;
}

/* Set group's current room */
int mempool_set_group_room(int group_id, int room_id)
{
	if (group_id >= g_pool_size || group_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct group_item *gi = &g_pool[group_id];
	
	gi->curr_room_id = room_id;

	fprintf(stderr, "Group %d set room as %d\n", group_id, room_id);
	
	return RTN_SUCCESS;
}

/* Get group's current runner */
int mempool_get_group_runner(int group_id)
{
	if (group_id >= g_pool_size || group_id < 0)
	{
		return RTN_GENERAL;
	}

	struct group_item *gi = &g_pool[group_id];
	
	return gi->curr_runner_id;
}

/* Get group's current room */
int mempool_get_group_room(int group_id)
{
	if (group_id >= g_pool_size || group_id < 0)
	{
		return RTN_GENERAL;
	}

	struct group_item *gi = &g_pool[group_id];

	return gi->curr_room_id;
}

/* Delete group */
int mempool_del_group(int group_id)
{
	int len = 0;
	int i;
	int *client_ids;
	
	if (group_id >= g_pool_size || group_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct group_item *gi = &g_pool[group_id];
	
	// Kick all clients
	client_ids = mempool_list_group_client(group_id, &len);
	
	for (i = 0; i < len; i ++)
	{
		mempool_set_client_group(client_ids[i], -1);
	}
	
	memset(gi, 0, sizeof(struct group_item));
	gi->group_id = -1;
	g_total --;
	
	return RTN_SUCCESS;
}

/* Get group properites */
unsigned long long mempool_get_group_pool(int group_id, int index)
{
	if (group_id >= g_pool_size || group_id < 0 || index >= MAX_GROUP_EXTENDS || index < 0)
	{
		return RTN_GENERAL;
	}
	
	struct group_item *gi = &g_pool[group_id];
	
	return gi->extends_pool[index];
}

/* Set group properites */
int mempool_set_group_pool(int group_id, int index, unsigned long long value)
{
	if (group_id >= g_pool_size || group_id < 0 || index >= MAX_GROUP_EXTENDS || index < 0)
	{
		return RTN_GENERAL;
	}
	
	struct group_item *gi = &g_pool[group_id];
	
	gi->extends_pool[index] = value;
	
	return RTN_SUCCESS;
}

/* Add Room */
int mempool_add_room(char *room_name)
{
	struct room_item *ri = (struct room_item *) NULL;
	int room_id = -1;
	int i;
	
	for (i = 0; i < r_pool_size; i ++)
	{
		ri = &r_pool[i];
		
		if (ri->room_id < 0)
		{
			room_id = i;
			break;
		}
	}
	
	if (room_id < 0)
	{
		// Enlarge room pool
		struct room_item *n = realloc(r_pool, r_pool_size * 2 * sizeof(struct room_item));
		if (!n)
		{
			log_add("Room pool enlarge error");
			return RTN_MEMORY_ERROR;
		}
		
		memset(&n[r_pool_size], 0, r_pool_size * sizeof(struct room_item));
		r_pool = n;
		
		for (i = 0; i < r_pool_size; i ++)
		{
			r_pool[r_pool_size + i].room_id = -1;
		}
		
		room_id = r_pool_size;
		ri = &r_pool[r_pool_size];
		
		r_pool_size *= 2;
	}
	
	ri->room_id = room_id;
	strncpy(ri->room_name, room_name, MAX_USERNAME_LENGTH);

	fprintf(stderr, "Room %s created as %d\n", room_name, room_id);
	log_add("Room %s created as %d", room_name, room_id);

	r_total ++;
	
	return room_id;
}

/* Set room's runner */
int mempool_set_room_runner(int room_id, int runner_id)
{
	if (room_id >= r_pool_size || room_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct room_item *ri = &r_pool[room_id];
	
	ri->curr_runner_id = runner_id;

	fprintf(stderr, "Room %d set runner as %d\n", room_id, runner_id);
	
	return RTN_SUCCESS;
}

/* Delete room */
int mempool_del_room(int room_id)
{
	if (room_id >= r_pool_size || room_id < 0)
	{
		return RTN_GENERAL;
	}
	
	struct room_item *ri = &r_pool[room_id];
	
	memset(ri, 0, sizeof(struct room_item));

	r_total --;
	
	return RTN_SUCCESS;
}

/* Get room properites */
unsigned long long mempool_get_room_pool(int room_id, int index)
{
	if (room_id >= r_pool_size || room_id < 0 || index >= MAX_ROOM_EXTENDS || index < 0)
	{
		return RTN_GENERAL;
	}
	
	struct room_item *ri = &r_pool[room_id];
	
	return ri->extends_pool[index];
}

/* Set room properites */
int mempool_set_room_pool(int room_id, int index, unsigned long long value)
{
	if (room_id >= r_pool_size || room_id < 0 || index >= MAX_ROOM_EXTENDS || index < 0)
	{
		return RTN_GENERAL;
	}
	
	struct room_item *ri = &r_pool[room_id];
	
	ri->extends_pool[index] = value;
	
	return RTN_SUCCESS;
}

/* Pool status */
int mempool_count_client()
{
	return c_total;
}

int mempool_count_group()
{
	return g_total;
}

int mempool_count_room()
{
	return r_total;
}

int mempool_count_base_client(int srv_id)
{
	return 0;
}

/* Return full client list */
struct client_item * mempool_cpool()
{
	return c_pool;
}

int mempool_cpool_size()
{
	return c_pool_size;
}

/* Return full group list */
struct group_item * mempool_gpool()
{
	return g_pool;
}

int mempool_gpool_size()
{
	return g_pool_size;
}
